﻿#region using

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using CodeDom;

#endregion

namespace SampleCodeDom
{
  /// <summary>
  /// This code example creates a graph using a CodeCompileUnit and  
  /// generates source code for the graph using the CSharpCodeProvider.
  /// </summary>
  internal class Sample
  {
    /// <summary>
    /// The name of the file to contain the source code.
    /// </summary>
    private const string outputFileName = "SampleCode.cs";

    /// <summary>
    /// The only class in the compile unit. This class contains 2 fields,
    /// 3 properties, a constructor, an entry point, and 1 simple method. 
    /// </summary>
    private readonly CodeTypeDeclaration targetClass;

    /// <summary>
    /// Define the compile unit to use for code generation. 
    /// </summary>
    private readonly CodeCompileUnit targetUnit;

    /// <summary>
    /// Define the class.
    /// </summary>
    public Sample()
    {
      targetUnit = new CodeCompileUnit();
      CodeNamespace samples = new CodeNamespace("CodeDOMSample");
      samples.Imports.Add(new CodeNamespaceImport("System"));
      targetClass = new CodeTypeDeclaration("CodeDOMCreatedClass");
      targetClass.IsClass = true;
      targetClass.TypeAttributes =
        TypeAttributes.Public | TypeAttributes.Sealed;
      samples.Types.Add(targetClass);
      targetUnit.Namespaces.Add(samples);
    }

    /// <summary>
    /// Adds two fields to the class.
    /// </summary>
    private void AddFields()
    {
      // Declare the widthValue field.
      CodeMemberField widthValueField = new CodeMemberField();
      widthValueField.Attributes = MemberAttributes.Private;
      widthValueField.Name = "widthValue";
      widthValueField.Type = new CodeTypeReference(typeof (Double));
      widthValueField.Comments.Add(new CodeCommentStatement(
                                     "The width of the object."));
      targetClass.Members.Add(widthValueField);

      // Declare the heightValue field
      CodeMemberField heightValueField = new CodeMemberField();
      heightValueField.Attributes = MemberAttributes.Private;
      heightValueField.Name = "heightValue";
      heightValueField.Type =
        new CodeTypeReference(typeof (Double));
      heightValueField.Comments.Add(new CodeCommentStatement(
                                      "The height of the object."));
      targetClass.Members.Add(heightValueField);
    }

    /// <summary>
    /// Add three properties to the class.
    /// </summary>
    private void AddProperties()
    {
      // Declare the read-only Width property.
      CodeMemberProperty widthProperty = new CodeMemberProperty();
      widthProperty.Attributes =
        MemberAttributes.Public | MemberAttributes.Final;
      widthProperty.Name = "Width";
      widthProperty.HasGet = true;
      widthProperty.Type = new CodeTypeReference(typeof (Double));
      widthProperty.Comments.Add(new CodeCommentStatement(
                                   "The Width property for the object."));
      widthProperty.GetStatements.Add(new CodeMethodReturnStatement(
                                        new CodeFieldReferenceExpression(
                                          new CodeThisReferenceExpression(), "widthValue")));
      targetClass.Members.Add(widthProperty);

      // Declare the read-only Height property.
      CodeMemberProperty heightProperty = new CodeMemberProperty();
      heightProperty.Attributes =
        MemberAttributes.Public | MemberAttributes.Final;
      heightProperty.Name = "Height";
      heightProperty.HasGet = true;
      heightProperty.Type = new CodeTypeReference(typeof (Double));
      heightProperty.Comments.Add(new CodeCommentStatement(
                                    "The Height property for the object."));
      heightProperty.GetStatements.Add(new CodeMethodReturnStatement(
                                         new CodeFieldReferenceExpression(
                                           new CodeThisReferenceExpression(), "heightValue")));
      targetClass.Members.Add(heightProperty);

      // Declare the read only Area property.
      CodeMemberProperty areaProperty = new CodeMemberProperty();
      areaProperty.Attributes =
        MemberAttributes.Public | MemberAttributes.Final;
      areaProperty.Name = "Area";
      areaProperty.HasGet = true;
      areaProperty.Type = new CodeTypeReference(typeof (Double));
      areaProperty.Comments.Add(new CodeCommentStatement(
                                  "The Area property for the object."));

      // Create an expression to calculate the area for the get accessor 
      // of the Area property.
      CodeBinaryOperatorExpression areaExpression =
        new CodeBinaryOperatorExpression(
          new CodeFieldReferenceExpression(
            new CodeThisReferenceExpression(), "widthValue"),
          CodeBinaryOperatorType.Multiply,
          new CodeFieldReferenceExpression(
            new CodeThisReferenceExpression(), "heightValue"));
      areaProperty.GetStatements.Add(
        new CodeMethodReturnStatement(areaExpression));
      targetClass.Members.Add(areaProperty);
    }

    /// <summary>
    /// Adds a method to the class. This method multiplies values stored 
    /// in both fields.
    /// </summary>
    private void AddMethod()
    {
      // Declaring a ToString method
      CodeMemberMethod toStringMethod = new CodeMemberMethod();
      toStringMethod.Attributes =
        MemberAttributes.Public | MemberAttributes.Override;
      toStringMethod.Name = "ToString";
      toStringMethod.ReturnType =
        new CodeTypeReference(typeof (String));

      CodeFieldReferenceExpression widthReference =
        new CodeFieldReferenceExpression(
          new CodeThisReferenceExpression(), "Width");
      CodeFieldReferenceExpression heightReference =
        new CodeFieldReferenceExpression(
          new CodeThisReferenceExpression(), "Height");
      CodeFieldReferenceExpression areaReference =
        new CodeFieldReferenceExpression(
          new CodeThisReferenceExpression(), "Area");

      // Declaring a return statement for method ToString.
      CodeMethodReturnStatement returnStatement =
        new CodeMethodReturnStatement();

      // This statement returns a string representation of the width,
      // height, and area.
      string formattedOutput = "The object:" + Environment.NewLine +
                               " width = {0}," + Environment.NewLine +
                               " height = {1}," + Environment.NewLine +
                               " area = {2}";
      returnStatement.Expression =
        new CodeMethodInvokeExpression(
          new CodeTypeReferenceExpression("System.String"), "Format",
          new CodePrimitiveExpression(formattedOutput),
          widthReference, heightReference, areaReference);
      toStringMethod.Statements.Add(returnStatement);
      targetClass.Members.Add(toStringMethod);
    }

    /// <summary>
    /// Add a constructor to the class.
    /// </summary>
    private void AddConstructor()
    {
      // Declare the constructor
      CodeConstructor constructor = new CodeConstructor();
      constructor.Attributes =
        MemberAttributes.Public | MemberAttributes.Final;

      // Add parameters.
      constructor.Parameters.Add(new CodeParameterDeclarationExpression(
                                   typeof (Double), "width"));
      constructor.Parameters.Add(new CodeParameterDeclarationExpression(
                                   typeof (Double), "height"));

      // Add field initialization logic
      CodeFieldReferenceExpression widthReference =
        new CodeFieldReferenceExpression(
          new CodeThisReferenceExpression(), "widthValue");
      constructor.Statements.Add(new CodeAssignStatement(widthReference,
                                                         new CodeArgumentReferenceExpression("width")));
      CodeFieldReferenceExpression heightReference =
        new CodeFieldReferenceExpression(
          new CodeThisReferenceExpression(), "heightValue");
      constructor.Statements.Add(new CodeAssignStatement(heightReference,
                                                         new CodeArgumentReferenceExpression("height")));
      targetClass.Members.Add(constructor);
    }

    /// <summary>
    /// Generate CSharp source code from the compile unit.
    /// </summary>
    /// <param name="fileName">Output file name</param>
    private void GenerateCSharpCode(string fileName)
    {
      CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
      CodeGeneratorOptions options = new CodeGeneratorOptions
                                       {
                                         BracingStyle = "Block",
                                         IndentString = "  "
                                       };
      using (StreamWriter sourceWriter = new StreamWriter(fileName))
      {
        provider.GenerateCodeFromCompileUnit(targetUnit, sourceWriter, options);
      }
    }

    /// <summary>
    /// Create the CodeDOM graph and generate the code.
    /// </summary>
    public void Generate()
    {
      AddFields();
      AddProperties();
      AddMethod();
      AddConstructor();
      GenerateCSharpCode(outputFileName);
    }
  }
}